use core::{convert::Infallible, net::Ipv4Addr};

use cmsis_rtos2::{
    error::Error,
    os_delay,
    static_rtos::event_flags::{EventFlags, EventFlagsMem},
};
use embedded_hal::{
    blocking::spi::{Transfer, Write},
    digital::v2::OutputPin,
};
use periph_utils::{GpioConfig, SpiConfig};
use stm32f1::stm32f103::interrupt;
use w5500_hl::{
    ll::{
        eh0::vdm::{self, W5500}, LinkStatus, Registers, Sn, SocketInterrupt, SocketInterruptMask, SocketStatus
    }, net::Eui48Addr, Tcp
};

use crate::utils::{UnsafeInitMut, UnsafeMut};

#[derive(GpioConfig, SpiConfig)]
#[gpio(
    init = "gpio_init",
    io(name = "reset", io = "PA2", mode = "OUT_PP", set, reset),
    io(name = "int", io = "PA3", mode = "IN_FLOAT", read, exti_down),
    io(name = "cs", io = "PA4", mode = "OUT_PP", set, reset,),
    io(name = "sck", io = "PA5", mode = "AF_PP"),
    io(name = "miso", io = "PA6", mode = "IN_FLOAT"),
    io(name = "mosi", io = "PA7", mode = "AF_PP")
)]
#[spi(
    init = "spi_init",
    spi = 1,
    baud_div = 2,
    size = 8,
    cpol = "low",
    cpha = "one"
)]
struct Init;

pub static NET: UnsafeMut<Net> = UnsafeMut::const_new();

pub fn init() -> Result<(), NetErr> {
    Init::gpio_init();
    Init::cs_set();
    Init::reset_reset();
    os_delay(50);
    Init::reset_set();
    os_delay(200);

    Init::spi_init();

    let net = Net::new()?;
    unsafe { NET.init(net); }

    use stm32f1::stm32f103::{Interrupt, NVIC};
    unsafe {
        NVIC::unmask(Interrupt::EXTI3);
    }

    NET.listen()?;
    Ok(())
}


pub type NetDriver = W5500<NetSpi, NetSpi>;
pub type NetErr = vdm::Error<Infallible, Infallible>;

pub struct Net {
    driver: UnsafeInitMut<NetDriver>,
    event_flags: EventFlags,
}

const MAC_ADDR: Eui48Addr = Eui48Addr::new(0x33, 0x44, 0x22, 0x11, 0x12, 0x13);
const IP_ADDR: Ipv4Addr = Ipv4Addr::new(192, 168, 1, 100);
const SUB_ADDR: Ipv4Addr = Ipv4Addr::new(255, 255, 255, 0);

const WEB_SERVER: Sn = Sn::Sn0;
const WEB_PORT: u16 = 8080;
static EVENT_FLAGS_MEM: EventFlagsMem = EventFlagsMem::const_new();

const FLAG_CON: u32 = 0x01;
const FLAG_DISCON: u32 = 0x02;
const FLAG_RECV: u32 = 0x04;
const FLAG_SEND_OK: u32 = 0x08;
const FLAG_TIMEOUT: u32 = 0x10;
const FLAG_INT: u32 = 0x20;

impl Net {
    fn new() -> Result<Self, NetErr> {
        let mut w5500 = W5500::new(NetSpi, NetSpi);
        w5500.set_shar(&MAC_ADDR)?;
        w5500.set_sipr(&IP_ADDR)?;
        w5500.set_subr(&SUB_ADDR)?;
        w5500.set_gar(&IP_ADDR)?;
        w5500.set_rtr(2000)?;
        w5500.set_rcr(3)?;

        w5500.set_simr(0x01)?;
        w5500.set_sn_imr(
            WEB_SERVER,
            SocketInterruptMask::ALL_MASKED
                .unmask_con()
                .unmask_discon()
                .unmask_recv()
                .unmask_sendok()
                .unmask_timeout(),
        )?;
        let event_flags = EventFlags::new("net", &EVENT_FLAGS_MEM);
        Ok(Self {
            driver: UnsafeInitMut::const_new(w5500),
            event_flags,
        })
    }

    pub fn read(&self, buf: &mut [u8]) -> Result<usize, NetErr> {
        let driver = unsafe { self.driver.as_mut() };
        let n = driver.tcp_read(WEB_SERVER, buf)?;
        Ok(n as usize)
    }

    pub fn read_to_empty(&self, buf: &mut [u8]) -> Result<usize, NetErr> {
        let mut index = 0;
        loop {
            if index >= buf.len() {
                defmt::println!("buf over:{}", index);
                break;
            }
            let n = self.read(&mut buf[index..])?;
            if n == 0 {
                break;
            }
            index += n;
        }
        Ok(index)
    }

    pub fn wait_read(&self) -> Result<(), NetErr> {
        loop {
            let status = self.poll_once()?;

            if status.discon_raised() || status.timeout_raised() {
                self.listen()?;
            }
            if status.con_raised() {
                defmt::println!("tcp accept");
            }
            if status.recv_raised() {
                break;
            }
        }
        Ok(())
    }

    pub fn listen(&self) -> Result<(), NetErr> {
        let driver = unsafe { self.driver.as_mut() };
        driver.tcp_listen(WEB_SERVER, WEB_PORT)?;
        Ok(())
    }
    
    fn poll_once(&self) -> Result<SocketInterrupt, NetErr> {
        let driver = unsafe { self.driver.as_mut() };

        while let Err(Error::Timeout) = self.event_flags.wait_any_flags(FLAG_INT, 500) {
            if driver.phycfgr()?.lnk() == LinkStatus::Down {
                let sn_sr = driver.sn_sr(WEB_SERVER)?.unwrap();
                if sn_sr != SocketStatus::Listen {
                    self.listen()?;
                }
            } else {
                let sn_ir = driver.sn_ir(WEB_SERVER)?;
                if sn_ir.any_raised() {
                    driver.set_sn_ir(WEB_SERVER, sn_ir)?;
                    return Ok(sn_ir);
                }
            }
        }
        let sn_ir = driver.sn_ir(WEB_SERVER)?;
        driver.set_sn_ir(WEB_SERVER, sn_ir)?;
        Ok(sn_ir)
    }

    pub fn write(&self, buf: &[u8]) -> Result<(), NetErr> {
        let driver = unsafe { self.driver.as_mut() };
        driver.tcp_write(WEB_SERVER, buf)?;
        Ok(())
    }

    fn on_interrupt(&self) {
        _ = self.event_flags.set_flags(FLAG_INT);
    }
}

pub struct NetSpi;

impl Transfer<u8> for NetSpi {
    type Error = Infallible;
    fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> {
        for v in words.iter_mut() {
            let n = Init::spi1_transfer(*v);
            *v = n;
        }
        Ok(words)
    }
}

impl Write<u8> for NetSpi {
    type Error = Infallible;
    fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> {
        for v in words {
            Init::spi1_transfer(*v);
        }
        Ok(())
    }
}

impl OutputPin for NetSpi {
    type Error = Infallible;
    fn set_high(&mut self) -> Result<(), Self::Error> {
        Init::cs_set();
        Ok(())
    }
    fn set_low(&mut self) -> Result<(), Self::Error> {
        Init::cs_reset();
        Ok(())
    }
}

#[interrupt]
unsafe fn EXTI3() {
    if Init::int_is_exti_pr() {
        Init::int_clear_exti_pr();
        NET.on_interrupt();
    }
}
